#include "WaterImp.h"

#include <assert.h>
#include <osg/Texture2D>
#include <osg/BlendFunc>
#include <osgDB/ReadFile>

#define WATERW 100
#define WATERH 100
#define WATERS 0.1

static osg::Texture2D* createTexture(const std::string& imgFilePath)
{
    osg::ref_ptr<osg::Texture2D> tex = new osg::Texture2D;
    tex->setFilter(osg::Texture::MAG_FILTER, osg::Texture::LINEAR);
    tex->setFilter(osg::Texture::MIN_FILTER, osg::Texture::LINEAR_MIPMAP_LINEAR);
    tex->setWrap(osg::Texture::WRAP_S, osg::Texture::REPEAT);
    tex->setWrap(osg::Texture::WRAP_T, osg::Texture::REPEAT);
    tex->setImage(osgDB::readImageFile(imgFilePath.c_str()));
    tex->setResizeNonPowerOfTwoHint(false);
    return tex.release();
}

/// x-y plane 
osg::Node* createWater(int w, int h, float s) 
{
    assert(w > 0);
    assert(h > 0);
    assert(s > 0);

    osg::ref_ptr<osg::Vec3Array> verts = new osg::Vec3Array;
    osg::ref_ptr<osg::Vec3Array> norms = new osg::Vec3Array;
    
    verts->reserve((w + 1)*(h + 1));
    norms->reserve(verts->size());
    for (int i = 0; i <= h; ++i)
    {
        for (int j = 0; j <= w; ++j)
        {
            verts->push_back(osg::Vec3(j*s, i*s, 0));
            norms->push_back(osg::Vec3(0, 0, 1));
        }
    }

    osg::ref_ptr<osg::DrawElementsUInt> elem = new osg::DrawElementsUInt();
    elem->setMode(GL_TRIANGLES);
    elem->reserve(6 * w * h);
    int wCount = w + 1;
    for (int i = 0; i < h; ++i)
    {
        for (int j = 0; j < w; ++j)
        {
            elem->addElement((i + 1)*wCount + j);
            elem->addElement(i*wCount + j);
            elem->addElement(i*wCount + j + 1);

            elem->addElement((i + 1)*wCount + j);
            elem->addElement(i*wCount + j + 1);
            elem->addElement((i + 1)*wCount + j + 1);
        }
    }

    osg::ref_ptr<osg::Geometry> geom = new osg::Geometry;
    geom->setVertexArray(verts);
    geom->setNormalArray(norms, osg::Array::BIND_PER_VERTEX);
    geom->addPrimitiveSet(elem);

    return geom.release();
}

osg::Node* createWaterNormalMap() 
{
    osg::ref_ptr<osg::Node> water = createWater(WATERW, WATERH, WATERS);
    osg::StateSet* ss = water->getOrCreateStateSet();
    {
        osg::ref_ptr<osg::Program> program = new osg::Program;
        program->addShader(osgDB::readShaderFile(osg::Shader::VERTEX, "./shader/normal_map_vs.glsl"));
        program->addShader(osgDB::readShaderFile(osg::Shader::FRAGMENT, "./shader/normal_map_fs.glsl"));
        ss->setAttributeAndModes(program);

        osg::ref_ptr<osg::Texture2D> tex = createTexture("./image/water_color.tif");
        ss->setTextureAttributeAndModes(0, tex);
        ss->addUniform(new osg::Uniform("tex", 0));

        osg::ref_ptr<osg::Texture2D> bumpTex = createTexture("./image/water_bump.jpg");
        ss->setTextureAttributeAndModes(1, bumpTex);
        ss->addUniform(new osg::Uniform("bumpTex", 1));

        ss->setAttributeAndModes(new osg::BlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA));

        ss->setRenderingHint(osg::StateSet::TRANSPARENT_BIN);
    }

    osg::ref_ptr<osg::Node> ground = createWater(1, 1, 10);
    osg::ref_ptr<osg::MatrixTransform> mt = new osg::MatrixTransform;
    mt->addChild(ground);
    mt->setMatrix(osg::Matrix::translate(0, 0, -0.1));
    {
        osg::ref_ptr<osg::Program> program = new osg::Program;
        program->addShader(osgDB::readShaderFile(osg::Shader::VERTEX, "./shader/mainTex_vs.glsl"));
        program->addShader(osgDB::readShaderFile(osg::Shader::FRAGMENT, "./shader/mainTex_fs.glsl"));
        mt->getOrCreateStateSet()->setAttributeAndModes(program);

        osg::ref_ptr<osg::Texture2D> tex = createTexture("./image/water_color.tif");
        mt->getOrCreateStateSet()->setTextureAttributeAndModes(0, tex);
        mt->getOrCreateStateSet()->addUniform(new osg::Uniform("tex", 0));
    }

    osg::ref_ptr<osg::Group> group = new osg::Group;
    group->addChild(water);
    group->addChild(mt);

    return group.release();
}
